﻿using fastNpoi.Interface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace fastNpoi.Impl
{
    public class ExpressionOperation : IDataOperation
    {
        private Dictionary<string, object> delegateCache=new Dictionary<string, object>();
        public object GetValue<T>(T data, PropertyInfo property) where T : class, new()
        {
            if(!delegateCache.TryGetValue($"get_{property.Name}",out object value))
            {
                ParameterExpression parameter = Expression.Parameter(typeof(T), "t");
                MemberExpression member = Expression.PropertyOrField(parameter, property.Name);
                UnaryExpression convertExpression = Expression.Convert(member, typeof(object));
                Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(convertExpression, parameter);
                var delegateValue = lambda.Compile();
                delegateCache.Add($"get_{property.Name}", delegateValue);
                return delegateValue.Invoke(data);
            }
            return (value as Func<T, object>).Invoke(data);
        }

        public void SetValue<T>(T data, PropertyInfo property, object value) where T : class, new()
        {
            if (!delegateCache.TryGetValue($"set_{property.Name}", out object cacheValue))
            {
                MethodInfo method = typeof(T).GetMethod($"set_{property.Name}");
                ParameterExpression parameter = Expression.Parameter(typeof(T), "t");
                ParameterExpression expValue = Expression.Parameter(typeof(object), "propertyValue");
                Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
                MethodCallExpression call = Expression.Call(parameter, method, Expression.Convert(expValue, t));
                Expression<Action<T, object>> lambda = Expression.Lambda<Action<T, object>>(call, parameter, expValue);
                var delegateValue = lambda.Compile();
                delegateCache.Add($"get_{property.Name}", delegateValue);
                delegateValue.Invoke(data,value);
            }
            (cacheValue as Action<T, object>).Invoke(data,value);
        }
    }
}
